package com.ficus.road.maintain.ditalTwin.service;


import com.ficus.road.maintain.config.DiseaseConfig;
import com.ficus.road.maintain.config.DiseaseConfigInfo;
import com.ficus.road.maintain.core.model.*;
import com.ficus.road.maintain.dao.HitDao;
import com.ficus.road.maintain.ditalTwin.dao.DigitalHitDao;
import com.ficus.road.maintain.grpc.RequestConverter;
import com.ficus.road.maintain.process.BaseProcessor;
import com.ficus.road.maintain.util.AliyunOSSUtil;
import com.ficus.road.maintain.util.IdGeneratorUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import ficus.car_demo.FrameMeta;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DigitalHitService {
    @Autowired
    DiseaseConfig diseaseConfig;
    @Autowired
    DigitalHitDao hitDao;
    @Autowired
    private HitDao eventDao;
    @Value("${hit-config.max-distance}")
    private int distance;
    @Value("${hit-config.merge-distance}")
    private int mergeDistance;
    @Autowired
    AliyunOSSUtil aliyunOSSUtil;
    private static final String prefix = "digitalTwin";
    public static Set<String> crackTypeSet = new HashSet<>() {
        {
            add("PIC_POTHOLES_RESULT_SETTLEMENT");
            add("PIC_CRACK_RESULT_PIC_CRACK");
            add("PIC_CRACK_RESULT_CRACKED");
        }
    };
    Map<String, Map<String, BaseProcessor.HitProperties>> lastHitMap = Maps.newConcurrentMap();

    public List<Hit> process(FrameMeta.FrameResult frameResult, String roadName, String picUrl) {
        FrameDTO value = RequestConverter.request2FrameDTO(frameResult, diseaseConfig);
        value.setPicturePath(picUrl);
        value.setPicture(picUrl);
        List<Hit> generateHits = generateHit(value, roadName);
        //去空
        List<Hit> hits = generateHits.stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());
        //将病害存入数据库(DataProcessor重写了该方法，ReplayDataProcessor重写了，但是是空方法)
        saveHitInfo(value, hits);
        return hits;
    }

    public void saveHitInfo(FrameDTO value, List<Hit> hits) {
        if (CollectionUtils.isNotEmpty(hits)) {
            createHits(hits);
            value.setHits(hits);
            hits.forEach(hit -> hit.setList(null));
        }
    }

    public void createHits(List<Hit> hits) {
        //新的逻辑，判断是否有历史告警
        hits.forEach(hit -> {
            //生成病害图并上传到OOS
            if (ObjectUtils.isNotEmpty(hit)) {
                // 井盖高差控制病害数量, 只把发过来的病害的10%
                //病害碰撞
                List<Hit> aggregate = aggregate(hit.getAttrs().getGpsLocation(), hit.getType());

                //病害merge
                if (CollectionUtils.isEmpty(aggregate)) {
                    hit.setId(hit.getAttrs().getCarId() + IdGeneratorUtil.generatorId());

                    hit.setList(Lists.newArrayList(HistoryHit.fromHit(hit)));
                    hit.setCreationTime(System.currentTimeMillis() / 1000);
                    hit.setLastModificationTime(System.currentTimeMillis() / 1000);
                    hitDao.insert(hit);
                } else {
                    // 该病害之前出现过，这里更新之前出现过的病害
                    Hit hitDB = aggregate.get(0);
                    hitDB.setLastModificationTime(System.currentTimeMillis() / 1000);
                    // 大于1个小时，更新一次历史
                    // 小于1小时不存，表示同一天只会存1次历史，且非复核病害才存历史（问题来了，复核过后为什么还会发现这个病害呢？）
                    if (hit.getHitTime() > (hitDB.getList().get(0).getHitTime() + 3600)) {
                        if (hitDB.getAttrs().getStatus().equals(HitStatusEnum.COMPLETE)) {
                            hitDB.getAttrs().getConfirmation().setStatus(Confirmation.ConfirmationStatus.TO_BE_CONFIRMED);
                        }
                        hit.getAttrs().setStatus(hitDB.getAttrs().getStatus());
                        hitDB.getList().add(0, HistoryHit.fromHit(hit));
                    }
                    hitDao.saveOrUpdate(hitDB);
                }
            }
        });
    }

    public List<Hit> aggregate(GpsLocation gpsLocation, String type) {
        org.springframework.data.geo.Point point = new Point(gpsLocation.getLongitude(), gpsLocation.getLatitude());
        List<AggregationOperation> aggregationList = new ArrayList<>();
        if (crackTypeSet.contains(type)) {
            aggregationList.add(new GeoNearDocument(new Query(Criteria.where("type").in(crackTypeSet)
                    .and("first_hit_time").gt(DateUtils.addDays(new Date(), -7).getTime() / 1000)
                    .and("attrs.status").in(HitStatusEnum.NEW, HitStatusEnum.DISPATCHED, HitStatusEnum.UNDISPATCH, HitStatusEnum.SEND, HitStatusEnum.PROCESSING, HitStatusEnum.COMPLETE, HitStatusEnum.CONFIRMFAILED)
                    .and("attrs.confirmation.status").in(Confirmation.ConfirmationStatus.CORRECT, Confirmation.ConfirmationStatus.TO_BE_CONFIRMED)), point, "location", mergeDistance));
        } else {
            aggregationList.add(new GeoNearDocument(new Query(Criteria.where("type").is(type)
                    .and("first_hit_time").gt(DateUtils.addDays(new Date(), -7).getTime() / 1000)
                    .and("attrs.status").in(HitStatusEnum.NEW, HitStatusEnum.DISPATCHED, HitStatusEnum.UNDISPATCH, HitStatusEnum.SEND, HitStatusEnum.PROCESSING, HitStatusEnum.COMPLETE, HitStatusEnum.CONFIRMFAILED)
                    .and("attrs.confirmation.status").in(Confirmation.ConfirmationStatus.CORRECT, Confirmation.ConfirmationStatus.TO_BE_CONFIRMED)), point, "location", mergeDistance));
        }
        Aggregation agg = Aggregation.newAggregation(
                aggregationList);
        List<Hit> aggregate = hitDao.aggregate(agg);
        return aggregate;
    }
    public List<Hit> generateHit(FrameDTO value, String roadName) {
        String carId = value.getCarId();
        //设计的时候是设计成一帧里可能有多个道路病害，但实际上就一个病害
        List<RoadDiseaseResult> roadDiseaseResult = value.getRoadDiseaseResult();
        List<Hit> hits = new ArrayList<>();
        Map<String, BaseProcessor.HitProperties> hitPropertiesMap = lastHitMap.getOrDefault(carId, new HashMap<>());
        GpsLocation currentGpsLocation = GpsLocation.builder()
                .longitude(value.getGpsLocation().getLongitude())
                .latitude(value.getGpsLocation().getLatitude())
                .build();
        if (CollectionUtils.isNotEmpty(roadDiseaseResult) && !value.getGpsLocation().getLongitude().equals(0d) && !value.getGpsLocation().getLatitude().equals(0d)) {
            Map<String, List<RoadDiseaseResult>> collect = roadDiseaseResult.stream().collect(Collectors.groupingBy(RoadDiseaseResult::getType));
            collect.forEach((type, diseaseResult) -> {
                BaseProcessor.HitProperties properties = hitPropertiesMap.get(type);
                Hit hit = createHit(value, diseaseResult, type);
                if (Objects.isNull(properties)) {
                    if (ObjectUtils.isNotEmpty(hit)) {
                        BaseProcessor.HitProperties build = BaseProcessor.HitProperties.builder()
                                .possibleHit(hit)
                                .firstHitGpsLocation(currentGpsLocation)
                                .build();
                        hitPropertiesMap.put(type, build);
                    }
                    hits.add(hit);
                } else {
                    double distance = GpsLocation.calDistance(properties.getFirstHitGpsLocation(), currentGpsLocation);
                    if (distance < distance) {

                    } else {
                        if (ObjectUtils.isNotEmpty(hit)) {
                            hit.getAttrs().setRoadName(roadName);
                            hits.add(hit);
                            properties.setFirstHitGpsLocation(currentGpsLocation);
                            properties.setPossibleHit(hit);
                            hitPropertiesMap.put(type, properties);
                        }
                    }
                }
            });
        }
        lastHitMap.put(carId, hitPropertiesMap);
        return hits;
    }

    public Confirmation.ConfirmationStatus getHitStatus() {
        return Confirmation.ConfirmationStatus.TO_BE_CONFIRMED;
    }
    private Hit createHit(FrameDTO value, List<RoadDiseaseResult> roadDiseaseResults, String type) {
        String finalType = type;
        List<HitSubject> subjects = roadDiseaseResults.stream().map(t -> HitSubject.builder()
                .bbox(t.getBbox())
                .relBbox(t.getRelBbox())
                .severityType(t.getSeverityType())
                .width(t.getWidth())
                .length(t.getLength())
                .area(t.getArea())
                .angle(t.getAngle())
                .heightDiff(t.getHeightDiff())
                .fallDownNumber(t.getFallDownNumber())
                .comfortRate(t.getComfortRate())
                .headCount(t.getHeadCount())
                .roadType(t.getRoadType())
                .needPushOut(t.getNeedPushOut())
                .type(finalType)
                .status(t.getStatus())
                //新增
                .diseaseUid(t.getDiseaseUid())
                .build()).collect(Collectors.toList());
        SeverityType maxType = null;
        String roadType = null;
        //新增
        String diseaseUid = null;
        int width = 0;
        float length = 0;
        float area = 0;
        float angle = 0;
        int heightDiff = 0;
        int fallDownNumber = 0;
        Boolean needPushOut = null;
        Integer headCount = null;
        Float comfortRate = null;
        HitStatusEnum hitStatusEnum = HitStatusEnum.NEW;

        //疑问：这里的循环是在干嘛？
        for (HitSubject subject : subjects) {
            if (maxType == null) {
                maxType = subject.getSeverityType();
                area = subject.getArea();
                length = subject.getLength();
                width = subject.getWidth();
                angle = subject.getAngle();
                heightDiff = subject.getHeightDiff();
                fallDownNumber = subject.getFallDownNumber();
                comfortRate = subject.getComfortRate();
                headCount = subject.getHeadCount();
                roadType = subject.getRoadType();
                needPushOut = subject.getNeedPushOut();
                type = subject.getType();
                hitStatusEnum = subject.getStatus();
                diseaseUid = subject.getDiseaseUid();

            } else {
                if (maxType.name().compareTo(subject.getSeverityType().name()) < 0) {
                    maxType = subject.getSeverityType();
                    area = subject.getArea();
                    length = subject.getLength();
                    width = subject.getWidth();
                    angle = subject.getAngle();
                    heightDiff = subject.getHeightDiff();
                    fallDownNumber = subject.getFallDownNumber();
                    type = subject.getType();
                    comfortRate = subject.getComfortRate();
                    headCount = subject.getHeadCount();
                    roadType = subject.getRoadType();
                    needPushOut = subject.getNeedPushOut();
                    hitStatusEnum = subject.getStatus();
                    //
                    diseaseUid = subject.getDiseaseUid();
                }
            }
        }
        long time = System.currentTimeMillis() / 1000;
        DiseaseConfigInfo diseaseConfigInfo = diseaseConfig.getMap().get(type);
        //只有在配置文件中的类型的病害才产生，其他的去掉
        if (Objects.nonNull(diseaseConfigInfo)) {
            Integer limitHour = diseaseConfigInfo.getLimitHour();
            String scene = diseaseConfigInfo.getScene();
            String classification = diseaseConfigInfo.getClassification();
            Integer loop = diseaseConfigInfo.getLoop();
            HitAttrs attrs = HitAttrs.builder()
                    .carId(value.getCarId())
                    .gpsLocation(GpsLocation.builder()
                            .latitude(value.getGpsLocation().getLatitude())
                            .longitude(value.getGpsLocation().getLongitude())
                            .build())
                    .location(Lists.newArrayList(value.getGpsLocation().getLongitude(), value.getGpsLocation().getLatitude()))
                    .sceneImage(value.getPicturePath())
                    .source(SourceEnum.NORMAL.toString())
                    .status(hitStatusEnum)
                    .picturePath(value.getPicturePath())
                    .roadType(roadType)
                    .limitHour(limitHour)
                    .confirmation(Confirmation.builder().status(getHitStatus()).build())
                    .loop(loop)
                    .diseaseUid(diseaseUid)
                    .build();
            //产生报警
            return Hit.builder()
                    .creationTime(time)
                    .firstHitTime(value.getTimeStamp() / 1000)
                    .hitTime(value.getTimeStamp() / 1000)
                    .type(type)
                    .lastModificationTime(time)
                    .subjects(subjects)
                    .attrs(attrs)
                    .width(width)
                    .area(area)
                    .length(length)
                    .angle(angle)
                    .heightDiff(heightDiff)
                    .fallDownNumber(fallDownNumber)
                    .comfortRate(comfortRate)
                    .headCount(headCount)
                    .roadType(roadType)
                    .scene(scene)
                    .classification(classification)
                    .needPushOut(needPushOut)
                    .severityType(maxType)
                    .algoVersion(value.getAlgoVersion())
                    .build();
        } else {
            log.info("final_type:{}", type);
        }
        return null;
    }

    public List<Hit> queryHits(Query query) {
        List<Hit> eventHitCommonDOS = hitDao.findByQuery(query);
        long currentTime = System.currentTimeMillis() / 1000;
        eventHitCommonDOS.forEach(item -> {
            Long dispatchTime = item.getDispatchTime();
            if (dispatchTime != null) {
                int discoverTime = (int) (currentTime - dispatchTime);
                Integer limitHour = item.getAttrs().getLimitHour();
                if (limitHour > 0) {
                    int restHour = limitHour - discoverTime / 3600;
                    item.getAttrs().setRestResolveHour(restHour);
                }
            }
        });
        return eventHitCommonDOS;
    }

    public int countHits(Query query) {
        return (int) hitDao.count(query);
    }

    public Hit getHit(String id) {
        Hit result = hitDao.fetch(id);
        //病害派单时间，病害什么时候派单？
        Long dispatchTime = result.getDispatchTime();
        //业务解释一下？
        if (dispatchTime != null) {
            long currentTime = System.currentTimeMillis() / 1000;
            int discoverTime = (int) (currentTime - dispatchTime);
            Integer limitHour = result.getAttrs().getLimitHour();
            if (limitHour > 0) {
                int restHour = limitHour - discoverTime / 3600;
                //更新病害剩余处理时间？
                result.getAttrs().setRestResolveHour(restHour);
            }
        }
        return result;
    }

    /**
     * 将三天内的锦绣东路的病害同步至数字孪生
     * @return
     */
    @Scheduled(cron = "0 10 4 * * ?")
    public int hitToDigitalHit() {
        // 拿到锦绣东路三天内的所有病害
        long aYearAgoTimestamp = System.currentTimeMillis() / 1000 - (86400 * 3);
        Query eventQuery = new Query();
        eventQuery.addCriteria(Criteria.where("attrs.road_name").is("锦绣东路").and("first_hit_time").gte(aYearAgoTimestamp));
//        eventQuery.addCriteria(Criteria.where("attrs.road_name").is("锦绣东路").and("_id").is("YTKJ20210063202401485932594"));
        List<Hit> eventByQuery = eventDao.findByQuery(eventQuery);

        Query hitQuery = new Query();
        hitQuery.addCriteria(Criteria.where("attrs.road_name").is("锦绣东路").and("first_hit_time").gte(aYearAgoTimestamp));
//        hitQuery.addCriteria(Criteria.where("attrs.road_name").is("锦绣东路"));
        List<Hit> hitByQuery = hitDao.findByQuery(hitQuery);
        Map<String, String> hitMap = hitByQuery.stream()
                .filter(hit -> hit.getAttrs().getCarId() != null && !hit.getAttrs().getCarId().isEmpty())
                .collect(Collectors.toMap(
                        hit -> hit.getAttrs().getCarId(),
                        Hit::getId
                ));

        int num = 0;
        for (Hit hit : eventByQuery) {
            String eventId = hitMap.get(hit.getId());
            hit.getAttrs().setCarId(hit.getId());
            hit.setId(eventId);
            Hit result = hitDao.saveOrUpdate(hit);
            num+=1;
        }

        return num;
    }

    /**
     * 将全量的锦绣东路的病害同步至数字孪生
     * 无需再运行，除非你明确自己在做什么
     * @return
     */
    public int hitToDigitalHitAll() {
        // 拿到锦绣东路三天内的所有病害
        long aYearAgoTimestamp = System.currentTimeMillis() / 1000 - (86400 * 3);
        Query eventQuery = new Query();
        eventQuery.addCriteria(Criteria.where("attrs.road_name").is("锦绣东路"));
        List<Hit> eventByQuery = eventDao.findByQuery(eventQuery);
        Query hitQuery = new Query();
        int num = 0;
        for (Hit hit : eventByQuery) {
            hit.getAttrs().setCarId(hit.getId());
            hit.setId(null);
            Hit result = hitDao.saveOrUpdate(hit);
            num+=1;
        }

        return num;
    }

    /**
     * 删除所有锦绣东路的病害
     * 无需再运行，除非你明确自己在做什么
     * @return
     */
    public int deleteHitToDigitalHitAll() {
        // 拿到锦绣东路三天内的所有病害
        Query eventQuery = new Query();
        eventQuery.addCriteria(Criteria.where("attrs.road_name").is("锦绣东路"));
        List<Hit> eventByQuery = hitDao.findByQuery(eventQuery);
        int num = 0;
        for (Hit hit : eventByQuery) {
            hitDao.deleteById(hit.getId());
            num+=1;
        }

        return num;
    }
}
